/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Server
 *
 * user.c: Implementierung der User-Verwaltung
 */

#include "common/sockets.h"
#include "user.h"
#include "sync.h"
#include "login.h"
#include "main.h"

pthread_mutex_t userdata_mutex;	/**< Mutex für Schutz der Userdaten */

static int client_slot[MAX_USERS] = { 0 };	/**< Client-Slots für Verwaltung freier/belegter User-Plätze */

/**	\brief Struktur für Userdaten */

typedef struct user_data {
	char name[32];
	int socket;
	uint32_t score;
	uint8_t id;
} USER_DATA;



typedef struct user_entry *USER_ENTRY_PTR;	/**< Pointer auf Userdaten */

/**	\brief Struktur für Knoten enthält Userdaten und Pointer auf nächsten Knoten */

typedef struct user_entry {
	USER_DATA data;
	USER_ENTRY_PTR next;
} USER_ENTRY;



static USER_ENTRY_PTR first_user_ptr = NULL;	/**< Initialisiere Pointer auf erstes Element der Liste */

/**	\brief Gibt Anzahl der User zurück */

uint8_t get_client_count() {
	uint8_t user_count = 0;
	USER_ENTRY_PTR tmp_user;
	tmp_user = first_user_ptr;
	// Nur ein Spieler
	if (tmp_user->next == NULL) {
		user_count++;
	} else {
		while (tmp_user != NULL) {
			user_count++;
			tmp_user = tmp_user->next;
		}
	}
	if (user_count == 0) {
		return NO_USERS;
	}
	return user_count;
}

/**	\brief Gibt Client Slots zurück */

int* get_client_slots() {
	return client_slot;
}

/**	\brief Gibt Socket zur entsprechenden Client-ID
 *	\param client */

int get_client_socket(uint8_t client) {
	USER_ENTRY_PTR tmp_user = first_user_ptr;
	while (tmp_user != NULL) {
		if (tmp_user->data.id == client) {
			return tmp_user->data.socket;
		}
		tmp_user = tmp_user->next;
	}
	return (-1);
}

/**	\brief Setzt neuen Punktestand
 * 	\param client Client ID
 * 	\param score erzielte Punkte*/

int set_client_score(uint8_t client, uint32_t score) {
	USER_ENTRY_PTR tmp_user = first_user_ptr;
	while (tmp_user != NULL) {
		if (tmp_user->data.id == client) {
			tmp_user->data.score = tmp_user->data.score + score;
			return 0;
		}
		tmp_user = tmp_user->next;
	}
	return (-1);
}

/**	\brief Gibt Pointer auf letztes Element der Liste */

USER_ENTRY_PTR get_last_user() {
	USER_ENTRY_PTR last_user;
	last_user = first_user_ptr;
	// Gehe durch existierende User-List-Elemente
	while (last_user->next != NULL) {
		// Weiter zu nächstem User-Entry
		last_user = last_user->next;
	};
	return last_user;
}

/**	\brief Fügt Client in User-Liste ein
 * 	\param socket Socket des Client
 * 	\param name Name des Client*/

uint8_t add_user(int client_socket, char* name) {
	uint8_t user_count = 0;

	// Index-Zeiger, Zeiger auf neuen User
	USER_ENTRY_PTR tmp_user_ptr, new_user_ptr;

	// Wenn kein Client in der Liste
	if (first_user_ptr == NULL) {
		// tmp_user_ptr = first_user_ptr wird nicht benötigt
	} else {
		// tmp_user_ptr auf erstes Element der Liste setzen
		tmp_user_ptr = first_user_ptr;
		tmp_user_ptr->next = first_user_ptr->next;

		// Gehe durch existierende User-List-Elemente
		do {
			// inkrementiere User-Zähler um 1
			user_count++;
			// Vergleiche Name des aktuellen Eintrags mit Name der Login-Anfrage
			if (strcmp(tmp_user_ptr->data.name, name) == 0) {
				debugPrint("User Name wird bereits verwendet!");
				return NAME_EXISTS;
			}
			// Wenn maximale Anzahl der User erreicht
			if (user_count >= MAX_USERS) {
				debugPrint("Server ist voll!");
				return SERVER_FULL;
			}
			// Weiter zu nächstem User-Entry
			tmp_user_ptr = tmp_user_ptr->next;
		} while (tmp_user_ptr != NULL);

	}

	// Speicher für neuen User-Eintrag holen
	new_user_ptr = (USER_ENTRY_PTR) malloc(sizeof(USER_ENTRY));
	// Fehler beim Speicher allokieren abfangen
	if (new_user_ptr == NULL) {
		debugPrint("Fehler beim erstellen der Stackelemente!");
		return UNKNOWN_ERROR;
	}

	// Fülle Userdaten
	// Suche freie Client-ID
	uint8_t client_id = 0;
	bool free_client_slot_found = false;
	while (client_id < MAX_USERS) {
		// Wenn Client-ID "i" nicht vergeben
		if (client_slot[client_id] == 0) {
			new_user_ptr->data.id = client_id;
			// Setze Client-ID als vergeben
			client_slot[client_id] = 1;
			free_client_slot_found = true;
			break;
		}
		client_id++;
	}
	if (free_client_slot_found != true) {
		free(new_user_ptr);
		new_user_ptr = NULL;
		debugPrint("Es konnte kein freier Client-Slot gefunden werden\n");
		return SERVER_FULL;
	}
	// Client Socket zur Kommunikation
	new_user_ptr->data.socket = client_socket;
	// Client Punktestand
	new_user_ptr->data.score = 0;
	// Kopiere neuen Namen in Userdaten Struktur
	strcpy(new_user_ptr->data.name, name);

	// Wenn Liste leer
	if (user_count == 0) {
		// Nächste Element = Ende der Liste
		new_user_ptr->next = NULL;
		// Erstes Element der Liste = neues Element
		first_user_ptr = new_user_ptr;
		first_user_ptr->next = NULL;
	} else {
		tmp_user_ptr = get_last_user();
		// Neues Element zeigt auf Ende der Liste
		new_user_ptr->next = NULL;
		// Altes Element der Liste zeigt auf neues Element
		tmp_user_ptr->next = new_user_ptr;
	}
	// new_user_ptr wieder auf NULL setzen, damit beim nächsten Malloc kein Speicherkonflikt
	new_user_ptr = NULL;

	// Ausgabe
	debugPrint("User %s wurde erfolgreich in die Userliste eingetragen\n", name);
	debugPrint("Die Client-ID lautet %d\n", client_id);

	return client_id;
}

/**	\brief Entfernt Client aus Userliste und schliesst Socket
 * 	\param client_socket Socket des Client*/

int remove_user(int client_socket) {
	USER_ENTRY_PTR tmp_user, erase_user;

	// Wenn Liste leer
	if (first_user_ptr == NULL) {
		int i;
		// Alle Client-Slots auf frei setzen
		for (i = 0; i < MAX_USERS; i++) {
			client_slot[i] = 0;
		}
		debugPrint("Es befinden sich keine Spieler auf dem Server");
		return NO_USERS;
	}

	// Setze Pointer auf erstes Element
	tmp_user = first_user_ptr;
	erase_user = first_user_ptr;

	// Wenn nur ein User in Liste
	if (tmp_user->next == NULL) {
		int i;
		// Alle Client-IDs frei
		for (i = 0; i < MAX_USERS; i++) {
			client_slot[i] = 0;
		}
		// Wenn Client Spielleiter
		if (tmp_user->data.id == 0) {
			close(tmp_user->data.socket);
			debugPrint("Spielleiter hat das Spiel verlassen\n");
			// Gib Speicher frei (Element löschen)
			free(tmp_user);
			tmp_user = NULL;
			//Setze Anfangspointer auf NULL
			first_user_ptr = NULL;
			// Gib zurück, dass Spielleiter Spiel verlassen hat
			return SPIELLEITER_LEFT;
		}
		// Wenn normaler Client
		else {
			close(tmp_user->data.socket);
			debugPrint("Client %s (ID: %d) hat das Spiel verlassen\n", tmp_user->data.name, tmp_user->data.id);
			// Gib Speicher frei (Element löschen)
			free(tmp_user);
			tmp_user = NULL;
			//Setze Anfangspointer auf NULL
			first_user_ptr = NULL;
			// Gib zurück, dass Client Spiel verlassen hat
			sem_post(&score_agent_sem);
			return CLIENT_LEFT;
		}
	}

	// Suche Element mit Socket "client_sock"
	while (tmp_user->next != NULL) {
		// Wenn zu löschendes Element = first_user_ptr (an erster Stelle der Liste)
		if (tmp_user->data.socket == client_socket) {
			// Speichere zu löschendes Element in Erase
			erase_user = tmp_user;
			// Setze Anfangs-Pointer
			// auf Element hinter zu löschendes Element
			// (Zu löschendes Element somit nicht mehr in Verkettung)
			first_user_ptr = tmp_user->next;

			// Wenn Client-ID = Spielleiter
			if (erase_user->data.id == 0) {
				// Setze Status der Client-ID auf "frei"
				client_slot[erase_user->data.id] = 0;
				close(erase_user->data.socket);
				// Gib Speicher frei (Element löschen)
				free(erase_user);
				erase_user = NULL;
				// Gib zurück, dass Spielleiter Spiel verlassen hat
				debugPrint("Spielleiter hat das Spiel verlassen\n");
				sem_post(&score_agent_sem);
				return SPIELLEITER_LEFT;
			} else {
				// Setze Status der Client-ID auf "frei"
				client_slot[erase_user->data.id] = 0;
				debugPrint("Client %s (ID: %d) hat das Spiel verlassen\n", tmp_user->data.name, tmp_user->data.id);
				close(erase_user->data.socket);
				// Gib Speicher frei (Element löschen)
				free(erase_user);
				erase_user = NULL;
				// Gib zurück, dass Client Spiel verlassen hat
				sem_post(&score_agent_sem);
				return CLIENT_LEFT;
			}
		}

		// Wenn Element, das vor Element mit Socket "client_sock" liegt,
		// gefunden wurde
		if (tmp_user->next->data.socket == client_socket) {
			// Speichere zu löschendes Element in Erase
			erase_user = tmp_user->next;
			// Setze next-Pointer des vorigen Elements
			// auf Element hinter zu löschendes Element
			// (Zu löschendes Element somit nicht mehr in Verkettung)
			tmp_user->next = tmp_user->next->next;

			// Wenn Client-ID = Spielleiter
			if (erase_user->data.id == 0) {
				// Setze Status der Client-ID auf "frei"
				client_slot[erase_user->data.id] = 0;
				close(erase_user->data.socket);
				// Gib Speicher frei (Element löschen)
				free(erase_user);
				erase_user = NULL;
				// Gib zurück, dass Spielleiter Spiel verlassen hat
				debugPrint("Spielleiter hat das Spiel verlassen\n");
				sem_post(&score_agent_sem);
				return SPIELLEITER_LEFT;
			} else {
				// Setze Status der Client-ID auf "frei"
				client_slot[erase_user->data.id] = 0;
				debugPrint("Client %s (ID: %d) hat das Spiel verlassen\n", erase_user->data.name, erase_user->data.id);
				// Gib Speicher frei (Element löschen)
				free(erase_user);
				erase_user = NULL;
				// Gib zurück, dass Client Spiel verlassen hat
				sem_post(&score_agent_sem);
				return CLIENT_LEFT;
			}
		}
		// Wähle nächstes Element der Liste
		tmp_user = tmp_user->next;
	}
	// Wenn sonst kein Return
	debugPrint("Beim entfernen eines Spielers ist ein Fehler aufgetreten");
	return UNKNOWN_ERROR;
}

/**	\brief Sortiert User-Liste nach Punkteständen */

int sort_user_list() {

	// Start bei erstem Eintrag
	USER_ENTRY_PTR compare_ptr = first_user_ptr;

	USER_ENTRY_PTR select_ptr = NULL;

	// Solange Zeiger auf nächsten Eintrag nicht Ende der Liste
	while (compare_ptr->next != NULL) {

		// Select User = nächster User nach Compare User
		select_ptr = compare_ptr->next;

		// Wenn Score von Compare User kleiner als Score des nächsten User
		if (compare_ptr->data.score < select_ptr->data.score) {

			// Wenn Select User größer als der erste User der Liste
			if (select_ptr->data.score > first_user_ptr->data.score) {

				// Verlinke nächsten User des Anfangs-User mit User hinter Select-User (3. User)
				compare_ptr->next = select_ptr->next;

				// Nächster User hinter Select-User ist der User am Anfang der Liste
				select_ptr->next = first_user_ptr;

				// Der neue erste User der Liste ist der Select-User
				first_user_ptr = select_ptr;

				debugPrint("Userliste sortiert");

				return 0;

			// Wenn Score von ausgewählten User nicht größer als Score des ersten User
			} else {

				// Verlinke nächsten User des Compare-User mit User hinter Select-User
				compare_ptr->next = select_ptr->next;

				// Setze Vergleichs-User auf Anfang
				compare_ptr = first_user_ptr;

				// Suche nach User, dessen Score kleiner als Select-User Score
				while (compare_ptr->next->data.score > select_ptr->data.score) {
					compare_ptr = compare_ptr->next;
				}

				// Nächster User hinter Select-User ist der User mit kleinerem Score als Select
				select_ptr->next = compare_ptr->next;

				// User mit größerem Score (Select-User) an richtige Stelle in Liste linken
				compare_ptr->next = select_ptr;

				debugPrint("Userliste sortiert");

				return 0;
			}
		}

		// Wähle nächsten User in der Liste
		compare_ptr = compare_ptr->next;

		// Wenn Ende der Liste erreicht, beende While-Schleife
		if (compare_ptr == NULL) {
			break;
		}
	}

	debugPrint("Userliste sortiert\n");

	return 0;
}

/**	\brief Bereitet Userliste auf und schreibt in übergebenes Char-Array
 * 	\param user_list Liste der angemeldeten User als String (RFC)*/

int get_user_list(char* user_list) {

	// Hole Spielstatus

	status_mutex_lock();

	int status = get_game_status();

	status_mutex_unlock();

	// Wenn Spiel läuft
	if (status == GAME_RUNNING) {
		sort_user_list();
	}

	USER_ENTRY_PTR tmp_user = NULL;
	tmp_user = first_user_ptr;

	int offset = 0;

	while (tmp_user != NULL) {

		memcpy(user_list + offset, tmp_user->data.name, 32);

		uint32_t tmp_score = htonl(tmp_user->data.score);

		memcpy(user_list + offset + 32, &tmp_score, 4);

		memcpy(user_list + offset + 36, &tmp_user->data.id, 1);

		tmp_user = tmp_user->next;

		offset = offset + 37;
	}

	return 0;
}
/**	\brief Hole das Ranking
 * 	\param client_id ClientID*/
int get_ranking(uint8_t* client_id) {

	int size = get_client_count();

	USER_ENTRY_PTR tmp_user = NULL;

	tmp_user = first_user_ptr;

	int offset = 0;

	while (tmp_user != NULL) {

		memcpy(client_id + offset, &tmp_user->data.id, 1);

		tmp_user = tmp_user->next;

		offset = offset + 1;
	}

	return size;
}

/**	\brief Initialisiert Mutex der Userdaten */

int create_userdata_mutex() {
	if (pthread_mutex_init(&userdata_mutex, NULL) != 0) {
		perror("Mutex-Fehler: ");
		return -1;
	}
	return 0;
}

/**	\brief Sperrt Zugriff auf Userdaten */

void userdata_mutex_lock() {
	pthread_mutex_lock(&userdata_mutex);
}

/**	\brief Gewährt Zugriff auf Userdaten */

void userdata_mutex_unlock() {
	pthread_mutex_unlock(&userdata_mutex);
}
